../.
SEAC10
MTCT10 - TEDA11
JAVP31
HCIK10 - LING11
FULO01
FOTO10
CCP411 - CCP421
  • Module CCD411 CCP421
  • stupunten 2
  • doelstelling
    Praktisch en vernieuwend met nieuwe media omgaan
  • oplevering:
    Verslag zn Demo

    Online messaging using KISS and apache

    inleiding

    Een elektronisch communicatiesysteem behelst, door de band genomen, 3 dingen

    • een afzender
    • een ontvanger
    • het bericht

    Goed beschouwd is er dus eigenlijk enkel een manier van authenticatie nodig om een messaging systeem op poten te zetten. Een manier om verzender en ontvanger van elkaar te identificeren, eventueel gekoppeld aan het tijdelijke of voor langere duur opslaan van data (het bericht)

    We gaan een communicatiesysteem te creeren dat ten alle tijden bereikbaar is, door firewalls heen en in nog onbekende netwerktopologien het gegarandeerd zal doen. De enige manier (i am aware of) om dit in deze wwweb-enabled tijden te doen is door alle communicatie over http heen te layeren.

    request based webverkeer is de allomtegenwoordige netwerklink die bedrijven huizen en organisaties met elkaar linkt, corporate firewalls en proxies blokeren wel een hoop sites, maar als het goed is zet je een altijd-overal-online messenger niet op zo'n server neer.

    voordelen bij keuze http(s) als transportmiddel

    authentication en dus een manier om gebruikers van elkaar te scheiden is inbegrepen in het protocol

    bij gebruik van ssl (https) wordt alle informatie encrypted en beveiligd voor man in the middle attacks

    nadelen bij keuze http(s) als transportmiddel

    http is een request based protocol waardoor enkel met een 'polling' model gewerkt kan worden om server en/of status berichten aan de client door te geven

    http is een protocol met redelijk veel overhead, zeker met korte berichten is het niet abnormaal als het gemiddelde paketje meer headers dan payload bevat.

    aan de slag,....

    vanwege de uitermate geschiktheid als prototyping language kiezen we perl als development taal

    voor standaard messaging doeleinden hebben we een paar dingen nodig

    • een lijst met users waarvan men de status wilt weten (contactlist)
    • een status en last seen time per user
    • een methode om berichten op te halen
    • een methode om berichten te verzenden
    • een spool directory om de berichten tijdelijk op te slaan

    als spool systeem hadden we kunnen opteren voor een SQL database backend maar de principes van KISS respecterend kiezen we voor het filesystem als data-storage / dbase oplossing.

    filesystem layout

    dirnamefunction
    /usrprogram data root
    /usr/USERNAMEuser homedir
    /usr/USERNAME/.statususer last seen / statusfile
    /usr/USERNAME/.alistcontact list (allowlist)
    /usr/USERNAME/.dlistshit list (denylist)
    /usr/USERNAME/USERNAMEincomming events from other user

    online messaging, breakdown

    het 'revolutionaire' (once upon a time) van icq en aol instant messenger was dat je de online status van je buddies kon zien. Softwarematig is dit eigenlijk niet zo'n byzondere feature; vanaf dat een client vraagt naar de status van anderen is zijn status gelijk geweten door de server. Dit gaat dus vanzelf 'goed'. het enige wat de server moet doen is vertellen wanneer hij zn contacts voor het laatst heeft gezien....

    execution flow

    standaard programma executie schema loopt als volgt

    1. welke (authenticated) user ben ik?
    2. zijn er wijzigingen in mn data uit te voeren
      1. status
      2. contactlist
    3. zet mijn lastseen time to huidige tijd
    4. zijn er ongelezen berichten
    5. indien gevraagd: toon berichten
    6. toon huidige status

    Om duidelijkheid in het programma te creeren bouw ik een prototype; hoewel een dergerlijke tussenstap tijdrovend kn zijn helpt het om je oplossing te toetsen aan de praktijk. Omdat ik voor prototyping perl gebruik kruipt er ook niet al teveel tijd in een dergelijke quick hack op te zetten

    bovenstaand command execution schema omgezet in regels code, filelocking wordt eventjes voor de eenvoudigheid weggelaten evenals Tainted data checks en andere, dit telt slechts als voorbeeld

    01 #!/usr/bin/perl
    02 # rapid prototype for online messenger
    03 # (c) 2004 GPL Matthijs P Dalhuijsen
    04 
    05 use CGI qw/:standard/;
    06 print header;
    07 my $root = "/var/www/thijs/tmp/oma";      
    08 my $timeout = 60*6;                   
    09 local $\=br;
    10 
    11 # welke (authenticated) user ben ik?
    12 defined $ENV{REMOTE_USER} || exit;              
    13                      
    14 # zijn er wijzigingen in mn data uit te voeren
    15 if (param('sendmsg')) {                                                       
    16 	if (-d "$root/".param('sendmsg')."/$ENV{REMOTE_USER}"){
    17		print "NYI";
    18	} else {
    19		print p, "user doesnt like you";
    20	}
    21 }
    22 # zet mijn lastseen time to huidige tijd
    23 open(SEEN,">$root/$ENV{REMOTE_USER}/.seen") || die("$!");
    24 print SEEN time;                                                                  
    25 close(SEEN);                      
    26 # zijn er ongelezen berichten
    27 foreach (<$root/$ENV{REMOTE_USER}/*/*>){               
    28	open MSG, $_;
    29	print $_;
    30	print <MSG>;
    31	close MSG;
    32	unlink $_
    33 }
    34 # toon huidige status
    35 open(CLIST,"$root/$ENV{REMOTE_USER}/.alist") || die("$!");  
    36 foreach(<CLIST>){                
    37	/^\s*(\#|$)/ && next;         
    38	chomp;                       
    39	open(SEEN,"$root/$_/.seen");
    40	if ((time - (<SEEN>))<$timeout) { 
    41		print "$_ is online";
    42	} else { 
    43		print "$_ is offline";
    44	}
    45 }
    
    

    Testomgeving OMA

    Met dit scriptje is de voornaamste functie van een instant messenger al geimplementeerd; de status van de contactpersonen wordt geregistreerd en weergegeven. Instant is ie dus al, nou nog messaging, ... hoewel dat je natuurlijk de telefoon erbij zou kunnen pakken.
    Functionaliteit moet getest worden en ik heb dan ook een testomgeving voor oma-prototype.pl neergezet; in deze test zijn 2 users aangemaakt; de user 'test' en de user 'thijs',
    de user test heeft een contactlist die buiten de user thijs ook zichzelf erop heeft staan, dit gebeurt in realiteit waarschijnlijk nooit, maar bij development is het handig om met jezelf te kunnen babbelen. De gebruikelijke hoeveelheden caffeine die bij development voorkomen beinvloeden het gesprek (de monoloog) vaak in dien mate dat er geregeld komische woordspelingen en zinsconstructies ontstaan. Je ziet zo'n programmeur dan soms ook grinnikend en grijnzend naar het scherm staren en op de vraag 'wat er zo te lachen?' het antwoord 'mezelf' opperen.

    hier kan je 'm testen. (username test pass test)

    interface

    een programma wordt tegenwoordig pas gesmaakt als het voorzien is van een mooie gui
    omdat we met html werken hoeven we gelukkig geen toolkit te kiezen, html bevat meer dan genoeg elementen om alles te kunnen bouwen wat je van je faforiete toolkit gewend bent. zeker als je javascript (dhtml) erbij rekent waardoor je een 'volwaardig' event model tot je beschikking hebt ipv enkel request based te kunnen werken

    als we javascript toevoegen letten we er op dat voor scriptloze browsers een alternatief voorzien wordt. elke kritieke functie (status, lezen, versturen) van de messenger moet in _elke_ browser draaien; maw. geen frames, geen plaatjes, geen javascript
    uiteraard mogen deze functies wel van bovenstaande dingen gebruik maken indien het client platform dit ondersteund

    Vormgeving

     [^_^] jan
     [o_o] jaap
     [-_-] bert
     [o_o] dirk
     [-_-] kees 

    options [o_o]
    als interface baseer ik me even op 90% vd bestaande messengers en neem een vorm aan als deze;
    contactlijst met status per contact, je eigen status wordt onderaan rechts weergegeven en een link voor extra opties
    als basis model voor een messenger moet dit volstaan dachtik

    implementatie

    omdat we willen dat elk hierboven geschetst onderdeel van de messenger onafhankelijk acties kan versturen en ontvangen maken we een frameset aan waarin de onderdelen zullen leven
    ik kies per default voor shtml ipv html, op deze manier is het makkelijk om later elementen toe te voegen of dynamisch te maken zonder dat de bestaande URI's hiervoor moeten wijzigen.

    index.shtml

    01 <html>
    02   <head>
    03     <title>messenger frameset</title>
    04   </head>
    05   <frameset rows="*,20" border=0 framespacing=0 framepadding=0 frameborder=0>
    06     <frame src="clist.pl" name=clist noresize>
    07     <frameset cols="*,30" border=0 framespacing=0 framepadding=0 frameborder=0>
    08       <frame src="options.shtml" name=options noresize>
    09       <frame src="status.pl" name=status>
    10     </frameset>
    11   </frameset>
    12   <noframes>
    13    <!--#Include virtual="noframes.pl" -->
    14   </noframes>
    15 </html>
    

    objectstructuur


    een objectgeorienteerde manier van werken is eigenlijk altijd een goed idee
    bij cgi scripting kan men af en toe het idee krijgen dat dit 'overkill' is voor de 'task at hand'
    vanaf dat je programma uit meerdere scripts bestaat is het echter een vereiste, alleen al uit onderhoudsoverwegingen

    in eerste instantie bouw ik de scripts, daarna de module
    dit lijkt een onlogische manier om te werk te gaan maar op deze manier werk je binnen je eigen testomgeving; vanaf dat de scripts doen wat ze moeten doen weet je dat je module en je api op punt staan
    het verdient nadruk dat dit enkel bij 'kleine' programma's een optie is; een volledig uitgewerkte api dient normaal te bestaan voordat er een letter geschreven wordt
    omdat er hier echter maar een viertal functies 'public' zijn kan deze stap overgeslagen worden

    stap 1, een lege module

    hoewel dat onze module in eerste instantie geen enkele functionaliteit zal bevatten moet hij wel even aangemaakt worden
    Messenger.pm
    01 package Messenger;
    02 use strict;
    03 sub new {    # CONSCRUCTOR
    04   my $proto = shift;
    05   $proto = ref $proto || $proto;
    06   my $self = {};
    07   bless ($self,$proto);
    08   return $self;
    09 }
    10 1
    

    stap 2, clist.pl

    vervolgens maken we clist.pl aan, de 2 voornaamste functies van dit scriptje zijn:
    • show online status of users
    • send message to specified user

    de programmalogica en communicatie die het scriptje met de module zal uitvoeren bij zijn eerste taak zal ongeveer zo gaan:
    bij elke user van mijn contactlist geef ik de status weer.
    in code zal dat dus iets zijn als

    foreach my $user (@clist) {
      print getstatus($user);
    }
    

    laten we deze eerste functie in script en module plaatsen
    clist.pl

    01 #!/usr/bin/perl
    02 print "Content-Type: text/html\n\n";
    03 use Messenger;
    04 my $msgr = Messenger->new();
    05 foreach ($msgr->clist()){
    06  printf "%s is %s<br>\n",
    07     $_,
    08     $msgr->status($_) ? "online" : "offline";
    09 }
    
    en in Messenger.pm komen er twee lege subjes bij:
    10 sub clist {
    11   my $self = shift;
    12   return "jan","klaas","piet","hein";
    13 }
    14 sub status {
    15   my $self = shift;
    16   return 1;
    17 }
    18 1
    

    options.shtml kan in eerste instantie leegelaten worden, later komen hier de contactlist functies en personal details onder

    01 <html>
    02   <head>
    03     <title>options</title>
    04   </head>
    05   <body>
    06  </body>
    07 </html>
    

    noframes.pl


    noframes.pl is perfect om als eerste volledig uit te bouwen, in deze pagina moet alle functionaliteit zitten van de messenger en kan door text only browsers ed gebruikt worden
    Het executieschema is hetzelfde als bij ons eerste prototype, maar ditmaal maken we gebruik van objecten
    01 use CGI qw/:standard/; 02 use Messenger; 03 my $msgr = Messenger->new(); 04 # welke user ben ik? 05 unless (defined $msgr->{user}) { 06 print "nomsgr"; 07 exit; 08 } 09 # setseen 10 $msgr->setseen(int time); 11 if (param('sendmsg')){ 12 if ($msgr->sendmessage(param('to'),param('message'))){ 13 print "messgae sent ok"; 14 } else { 15 print "some error occurred $!"; 16 } 17 } 18 19 local $\=br; 20 map { print $msgr->status($_) ? "$_ online" : "$_ offline" } $msgr->clist;

    De meeste functionaliteit zit wel in noframes.pl dus kan dit voorlopig als testinterface dienen en kan Messenger.pm uitgebreid worden met enkele ontbrekende functies. Nadat de laatste stubjes overgezet zijn is de API waarschijnlijk volledig;

    01 package Messenger;
    02 use strict;
    03 sub new {    # CONSCRUCTOR
    04   my $proto = shift;
    05   $proto = ref $proto || $proto;
    06   my $self = {};
    07   bless ($self,$proto);
    08   return $self;
    09 }
    10 sub clist {
    11   my $self = shift;
    12   return "jan","klaas","piet","hein";
    13 }
    14 sub status {
    15   my $self = shift;
    16   return 1;
    17 }
    18 sub getseen {
    19   my $self = shift;
    20   return int time;
    21 }
    22 sub setseen {
    23   my $self = shift;
    24   return 1;
    25 }
    26 sub sendmessage {
    27   my ($self, $to, $message) = @_;
    28   return 0;
    29 }
    30 sub readmessage {
    31   my $self = shift;
    32   my $from = shift;
    33   return "leeg bericht";
    34 }
    35 1
    

    en kunnen de stub-routines vervangen worden door sub-routines. dit is eigenlijk een kwestie van de acties in het prototype scriptje om te zetten naar de net aangemaakte stub module

    01 package Messenger;
    02 use strict;
    
    in de constructor komen wat settings hardcoded te staan; deze kunnen natuurlijk altijd overschreven worden: het blijft perl
    03 sub new {    # CONSTRUCTOR
    04   my $proto = shift;
    05  $proto = ref $proto || $proto;
    06  my $self = {};
    07  bless ($self,$proto);
    08  $self->{timeout} = 60*6;
    09  $self->{root} = $ENV{HOME} . "/tmp/oma";
    10  $self->{user} = $ENV{REMOTE_USER};
    11  $self->{home} = $self->{root} . "/" . $self->{user};
    12  return $self;
    13 }
    
    clist opent eerst de gebruikers contactlist en haalt dan op lijn 17 de newline achter elke naam weg. Hier kan ook een cache geimplementeerd worden zodat niet elke actie die de contactlist gebruikt steeds het filesystem aanspreekt
    14 sub clist {
    15   my $self = shift;
    16   open(CLIST,$self->{home}."/.alist") || return 0;
    17   map { chomp; +("$_") } (<CLIST>);
    18 }
    
    dan de functies om de 'lastseen' tijd te updaten en te verkrijgen, de sub status kijkt of de gevraagde gebruiker minder dan {timeout} tijd geleden op de server gezien is
    19 sub status {
    20  my ($self,$req) = @_;
    21  return ( int(time) - $self->getseen($req)) < $self->{timeout};
    22 }
    23 sub getseen {
    24   my ($self,$req) = @_;
    25   open(SEEN,$self->{root}.'/'.quotemeta($req).'/.seen') || return 0;
    26   $req=<SEEN>;chomp;return int $req;
    27 }
    28 sub setseen {
    29   my $self = shift;
    30   open(SEEN,">".$self->{home}.'/.seen') || return 0;
    31   print SEEN int time;
    32   close(SEEN);
    33 }
    

    en dan de 2 functies om berichten te lezen en te versturen, er wordt geen history bijgehouden dus nadat het bericht gelezen is mag het verwijderd worden

    34 sub sendmessage {
    35   my ($self, $to, $message) = @_;
    36   my $msgdir = $self->{root}.'/'.quotemeta($to).'/'.quotemeta($self->{user});
    37   if ( -d $msgdir ) {
    38     open(MSG,">>".$msgdir.'/'.int(time)) || return 0;
    39     print MSG $message;
    40     close MSG;
    41     return 1;
    42   } else {
    43     return 0;
    44   }
    45 }
    46 sub readmessage {
    47   my $self = shift;
    48   my $from = shift;
    49   my $msgloc = $self->{root}.'/'.quotemeta($self->{user}).'/'.quotemeta($from).'/msg';
    50   if ( -f $msgloc ) {
    51     open(MSG,$msgloc);
    52     my @tmpo = (<MSG>);
    53     close MSG;
    54     unlink $msgloc;
    55     return @tmpo;
    56   } else {
    57     return 0;
    58   }
    59 }
    60 1
    


    referenties: perldoc cpan
  • ANIM31